package com.ccod.mapper.mapper.core.chain.process;

import com.ccod.mapper.mapper.core.chain.CoreServiceProcess;
import com.ccod.mapper.mapper.core.model.MapperRefreshContext;
import com.ccod.mapper.mapper.core.utils.RefreshUtils;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * 重新载入xml资源
 *
 * @author: ccod
 * @data: 2020/12/5 13:05
 **/
public class RefreshResourceProcess implements CoreServiceProcess<MapperRefreshContext> {


    @Override
    public void process(MapperRefreshContext coreContext) throws Exception {
        InputStream in1 = null;
        InputStream in2 = null;
        try{
            Configuration configuration = coreContext.getConfiguration();
            File mapperFile = coreContext.getMapperFile();
            Field loadedResourcesField = null;
            Class<?> configurationSuper = configuration.getClass().getSuperclass();
            if (configuration.getClass().getSuperclass() != null && configurationSuper != Object.class) {
                loadedResourcesField = configuration.getClass().getSuperclass().getDeclaredField("loadedResources");
            }else {
                loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");
            }
            loadedResourcesField.setAccessible(true);
            Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
            in1 = new FileInputStream(mapperFile);
            XPathParser xPathParser = new XPathParser(in1, true, configuration.getVariables(),
                    new XMLMapperEntityResolver());
            XNode context = xPathParser.evalNode("/mapper");
            String namespace = context.getStringAttribute("namespace");
            Field field = MapperRegistry.class.getDeclaredField("knownMappers");
            field.setAccessible(true);
            Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
            mapConfig.remove(Resources.classForName(namespace));
            loadedResourcesSet.remove(mapperFile.toString());
            configuration.getCacheNames().remove(namespace);
            cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace, configuration);
            cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace, configuration);
            cleanKeyGenerators(context.evalNodes("select|insert|update|delete"), namespace, configuration);
            cleanSqlElement(context.evalNodes("/mapper/sql"), namespace, configuration);
            in2 = new FileInputStream(mapperFile);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in2,
                    configuration,
                    mapperFile.toString(), configuration.getSqlFragments());
            xmlMapperBuilder.parse();
        }finally {
            RefreshUtils.close(in1);
            RefreshUtils.close(in2);
        }
    }


    @Override
    public boolean isInterrupt(MapperRefreshContext context) {
        return context.isInterrupt();
    }

    /**
     * 清理parameterMap
     *
     * @param list
     * @param namespace
     */

    protected void cleanParameterMap(List<XNode> list, String namespace, Configuration configuration) {
        for (XNode parameterMapNode : list) {
            String id = parameterMapNode.getStringAttribute("id");
            configuration.getParameterMaps().remove(namespace + "." + id);
        }
    }


    /**
     * 清理resultMap
     *
     * @param list
     * @param namespace
     */

    protected void cleanResultMap(List<XNode> list, String namespace, Configuration configuration) {
        for (XNode resultMapNode : list) {
            String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
            configuration.getResultMapNames().remove(id);
            configuration.getResultMapNames().remove(namespace + "." + id);
            clearResultMap(resultMapNode, namespace, configuration);
        }
    }


    protected void clearResultMap(XNode xNode, String namespace, Configuration configuration) {
        for (XNode resultChild : xNode.getChildren()) {
            if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
                    || "case".equals(resultChild.getName())) {
                if (resultChild.getStringAttribute("select") == null) {
                    configuration.getResultMapNames().remove(
                            resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
                    configuration.getResultMapNames().remove(
                            namespace + "." + resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
                    if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
                        clearResultMap(resultChild, namespace, configuration);
                    }
                }
            }
        }
    }


    /**
     * 清理selectKey
     *
     * @param list
     * @param namespace
     */

    protected void cleanKeyGenerators(List<XNode> list, String namespace, Configuration configuration) {
        for (XNode context : list) {
            String id = context.getStringAttribute("id");
            configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
            configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
            configuration.getMappedStatementNames().remove(id);
            configuration.getMappedStatementNames().remove(namespace + "." + id);
        }
    }


    /**
     * 清理sql节点缓存
     *
     * @param list
     * @param namespace
     */

    protected void cleanSqlElement(List<XNode> list, String namespace, Configuration configuration) {
        for (XNode context : list) {
            String id = context.getStringAttribute("id");
            configuration.getSqlFragments().remove(id);
            configuration.getSqlFragments().remove(namespace + "." + id);
        }

    }

}
